package jass.contact;
import jass.engine.*;
import jass.generators.*;

/**
   A force model with impact, slide, and slide modes based on AR2 model for
   sliding, wavetable model as in ContactForce for rolling and impact.
   @author Kees van den Doel (kvdoel@cs.ubc.ca)
*/
public class ARContactForce extends ContactForce {
    boolean isComplexReson = false;

    // contains reson filter which is fed white noise
    private FilterContainer slideForce;
    
    // reson filter for sliding (AR2 model)
    private ResonFilter resonSlideFilter;
    
    // white noise source
    private ConstantLoopBuffer loopedNoise;
    
    // freq., damp. and gain of AR model (Reson).
    private float ARd=1000f,ARf=100f,ARa=1f,maxARd=ARd; // max is value at max freq.
    
    // min and max reson values
    private float slideFreq0=10f,slideFreq1=2000f;
    
    /** Constructor intended only for subclass constructors (super(bufferSize);)
     */
    protected ARContactForce(int bufferSize) {
        super(bufferSize);
    }
    
    /** Construct contact force from named files.
        @param srate sampling rate in Hertz.
        @param bufferSize bufferSize of this Out.
        @param fnImpact Audio file name for impact. (For example cos20ms.wav.)
        @param fnRoll Audio file name for slide. (For example white5.wav.) Will not be pitch shifted but filtered with AR model.
        @param fnRoll Audio file name for slide. (For example roll.wav.)
    */
    public ARContactForce(float srate, int bufferSize, String fnImpact,String fnSlide,String fnRoll) {
        super(bufferSize);
        bangForce = new BangForce(srate,bufferSize,fnImpact);
        rollForceRaw = new LoopBuffer(srate,bufferSize,fnRoll);
        // build slide source as loop buffer feeding into reson filter
        resonSlideFilter = new ResonFilter(srate);
        loopedNoise = new ConstantLoopBuffer(srate,bufferSize,fnSlide);
        slideForce = new FilterContainer(srate,bufferSize,resonSlideFilter);
        if(isComplexReson) {
            resonSlideFilter.setResonCoeff(-ARf,ARd,ARa);
        } else {
            resonSlideFilter.setResonCoeff(ARf,ARd,ARa);
        }
        try {
            slideForce.addSource(loopedNoise);
        } catch(SinkIsFullException e) {
            System.out.println(this+" "+e);
        }
        
        bangForce.setTime(getTime());
        slideForce.setTime(getTime());
        rollForceRaw.setTime(getTime());
        lowPassFilter = new Butter2LowFilter(srate);
        lowPassFilter.setCutoffFrequency(fLowPass);
        rollForce = new FilterContainer(srate,bufferSize,lowPassFilter);
        try {
            rollForce.addSource(rollForceRaw);
        } catch(SinkIsFullException e) {
            System.out.println(this+" "+e);
        }
        rollForce.setTime(getTime());
    }


    /** Set model parameters mapping physical units to audio units
        @param slideFreq0 minimum reson freq. (should be zero)
        @param slideFreq1 maximum reson freq. (should correspond to max velocity)
        @param rollSpeed1 maximum audio loop speed (1 = original recording)
        @param vslide0 minimum physical speed (lower than this is considered to be zero)
        @param vslide1 maximum physical speed (higher than this is set to this value)
        @param vroll0 minimum physical speed (lower than this is considered to be zero)
        @param vroll1 maximum physical speed (higher than this is set to this value)
        @param physicalToAudioGainSlide multiplies normal force to get slide gain
        @param physicalToAudioGainRoll multiplies normal force to get roll gain
        @param physicalToAudioGainImpact multiplies impact force to get impact gain
        @param boolean isComplexReson if is complex reson (all freq negative)
     */
    public void setStaticContactModelParameters(float slideFreq0, float slideFreq1, float rollSpeed1, float vslide0,
                                                float vslide1, float vroll0, float vroll1,
                                                float physicalToAudioGainSlide,
                                                float physicalToAudioGainRoll,float physicalToAudioGainImpact,
                                                boolean isComplexReson) {
        this.slideFreq0 = slideFreq0;
        this.slideFreq1 = slideFreq1;
        this.rollSpeed1 = rollSpeed1;
        this.vslide0 = vslide0;
        this.vslide1 = vslide1;
        this.vroll0  = vroll0;
        this.vroll1  = vroll1;
        this.physicalToAudioGainSlide = physicalToAudioGainSlide;
        this.physicalToAudioGainRoll = physicalToAudioGainRoll;
        this.physicalToAudioGainImpact = physicalToAudioGainImpact;
        this.isComplexReson = isComplexReson;
    }

    

    /** Set slide model damping (usually static property).
        This is actually the damping at maximum velocity and scales down
        with the reson freq.
        @param d damping
     */
    public void setSlideModelDamping(float d) {
        this.maxARd = d;
    }
    
    /** Set slide speed and normal force.
        @param force normal force.
        @param speed relative surface velocity. (Audio file is speed = 1.)
     */
    public synchronized void setSlideProperties(float force,float speed) {
        // f= a*v +b;
        float a = (slideFreq1-slideFreq0)/(vslide1-vslide0);
        float b = slideFreq0 - a*vslide0;
        float f,d; // freq and gain
        
        float vol = (float)(Math.sqrt(force * speed/vslide1));
        if(speed > vslide1) {
            ARd = maxARd;
            speed = vslide1;
            ARf = slideFreq1;
            ARa = physicalToAudioGainSlide*vol;
        } else if(speed < vslide0) {
            ARf = slideFreq1;
            ARa = 0;
            ARd = maxARd;
        } else {
            ARf = a*speed + b;
            ARd = maxARd * speed/vslide1;
            ARa = physicalToAudioGainSlide*vol;
        }
        if(isComplexReson) {
            resonSlideFilter.setResonCoeff(-ARf,ARd,ARa);
        } else {
            resonSlideFilter.setResonCoeff(ARf,ARd,ARa);
        }
    }

    /** Compute the next buffer.
     */
    public synchronized void computeBuffer() {
        try {
            float[] b1 = bangForce.getBuffer(getTime());
            float[] b2 = slideForce.getBuffer(getTime());
            float[] b3 = rollForceRaw.getBuffer(getTime());
            float[] b4 = rollForce.getBuffer(getTime());
            int bufsz = getBufferSize();
            for(int k=0;k<bufsz;k++) {
                buf[k] = b1[k] + b2[k] + dryRollGain*b3[k] + (1 - dryRollGain)*b4[k];
            }
        } catch(BufferNotAvailableException e) {
            System.out.println(this+" "+e);
        }
    }

}








